// Copyright (c) 2012 Chan Wai Shing
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package syntaxhighlight;

import java.awt.Color;
import java.awt.Font;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.swing.text.SimpleAttributeSet;

/**
 * Theme for the {@link SyntaxHighlighterPane} and 
 * {@link JTextComponentRowHeader}.
 * 
 * To make a new theme, either extending this class or initiate this class and 
 * set parameters using setters. For the default value, find the comment of the 
 * constructor.
 * 
 * @author Chan Wai Shing <cws1989@gmail.com>
 */
public class Theme {

  private static final Logger LOG = Logger.getLogger(Theme.class.getName());
  /**
   * The font of the script text.
   */
  protected Font font;
  /**
   * The background color of the script text area.
   */
  protected Color background;
  /**
   * The background color of the highlighted line of script text.
   */
  protected Color highlightedBackground;
  /**
   * Gutter (line number column on the left)
   */
  /**
   * The color of the gutter text.
   */
  protected Color gutterText;
  /**
   * The color of the border that joint the gutter and the script text area.
   */
  protected Color gutterBorderColor;
  /**
   * The width of the border that joint the gutter and the script text area.
   */
  protected int gutterBorderWidth;
  /**
   * The font of the gutter text.
   */
  protected Font gutterTextFont;
  /**
   * The minimum padding from 'the leftmost of the line number text' to 
   * 'the left margin'.
   */
  protected int gutterTextPaddingLeft;
  /**
   * The minimum padding from 'the rightmost of the line number text' to 
   * 'the right margin' (not to the gutter border).
   */
  protected int gutterTextPaddingRight;
  /**
   * Text area.
   */
  /**
   * The default style. When the style requested by {@link #getStyle(String)} 
   * not exist, this will be returned.
   */
  protected Style plain;
  /**
   * The styles of this theme.
   */
  protected Map<String, Style> styles;

  /**
   * Constructor.<br />
   * <p>
   * <b>Default value:</b><br />
   * <ul>
   * <li>font: Consolas 12pt</li>
   * <li>background: white</li>
   * <li>gutter text: black</li>
   * <li>gutter border: R: 184, G: 184, B: 184</li>
   * <li>gutter border width: 3px</li>
   * <li>gutter text font: Consolas 12pt</li>
   * <li>gutter text padding-left: 7px</li>
   * <li>gutter text padding-right: 7px</li>
   * </ul>
   * </p>
   */
  public Theme() {
    font = new Font("Consolas", Font.PLAIN, 12);
    background = Color.white;

    highlightedBackground = Color.gray;

    gutterText = Color.black;
    gutterBorderColor = new Color(184, 184, 184);
    gutterBorderWidth = 3;
    gutterTextFont = new Font("Consolas", Font.PLAIN, 12);
    gutterTextPaddingLeft = 7;
    gutterTextPaddingRight = 7;

    plain = new Style();

    styles = new HashMap<String, Style>();
  }

  /**
   * Apply the theme to the row header panel.
   * @param rowHeader the row header to apply the theme on
   */
  public void setTheme(JTextComponentRowHeader rowHeader) {
    rowHeader.setBackground(background);
    rowHeader.setHighlightedColor(background);

    rowHeader.setForeground(gutterText);
    rowHeader.setBorderColor(gutterBorderColor);
    rowHeader.setBorderWidth(gutterBorderWidth);
    rowHeader.setFont(gutterTextFont);
    rowHeader.setPaddingLeft(gutterTextPaddingLeft);
    rowHeader.setPaddingRight(gutterTextPaddingRight);
  }

  /**
   * Set the default style.
   * @param plain the style
   */
  public void setPlain(Style plain) {
    if (plain == null) {
      throw new NullPointerException("argument 'plain' cannot be null");
    }
    this.plain = plain;
  }

  /**
   * Get the default style.
   * @return the style
   */
  public Style getPlain() {
    return plain;
  }

  /**
   * Get the {@link AttributeSet} of {@code styleKeys}. For more than one 
   * styles, separate the styles by space, e.g. 'plain comments'.
   * @param styleKeys the style keys with keys separated by space
   * @return the combined {@link AttributeSet}
   */
  public SimpleAttributeSet getStylesAttributeSet(String styleKeys) {
    if (styleKeys.indexOf(' ') != -1) {
      SimpleAttributeSet returnAttributeSet = new SimpleAttributeSet();
      String[] _keys = styleKeys.split(" ");
      for (String _key : _keys) {
        returnAttributeSet.addAttributes(getStyle(_key).getAttributeSet());
      }
      return returnAttributeSet;
    } else {
      return getStyle(styleKeys).getAttributeSet();
    }
  }

  /**
   * Add style.
   * @param styleKey the keyword of the style
   * @param style the style
   * @return see the return value of {@link Map#put(Object, Object)}
   */
  public Style addStyle(String styleKey, Style style) {
    return styles.put(styleKey, style);
  }

  /**
   * Remove style by keyword.
   * @param styleKey the keyword of the style
   * @return see the return value of {@link Map#remove(Object)}
   */
  public Style removeStyle(String styleKey) {
    return styles.remove(styleKey);
  }

  /**
   * Get the style by keyword.
   * @param key the keyword
   * @return the {@link syntaxhighlighter.theme.Style} related to the 
   * {@code key}; if the style related to the {@code key} not exist, the 
   * style of 'plain' will return.
   */
  public Style getStyle(String key) {
    Style returnStyle = styles.get(key);
    return returnStyle != null ? returnStyle : plain;
  }

  /**
   * Get all styles.
   * @return the styles
   */
  public Map<String, Style> getStyles() {
    return new HashMap<String, Style>(styles);
  }

  /**
   * Clear all styles.
   */
  public void clearStyles() {
    styles.clear();
  }

  /**
   * The font of the script text.
   * @return the font
   */
  public Font getFont() {
    return font;
  }

  /**
   * The font of the script text.
   * @param font the font
   */
  public void setFont(Font font) {
    if (font == null) {
      throw new NullPointerException("argument 'font' cannot be null");
    }
    this.font = font;
  }

  /**
   * The background color of the script text area.
   * @return the color
   */
  public Color getBackground() {
    return background;
  }

  /**
   * The background color of the script text area.
   * @param background the color
   */
  public void setBackground(Color background) {
    if (background == null) {
      throw new NullPointerException("argument 'background' cannot be null");
    }
    this.background = background;
  }

  /**
   * The background color of the highlighted line of script text.
   * @return the color
   */
  public Color getHighlightedBackground() {
    return highlightedBackground;
  }

  /**
   * The background color of the highlighted line of script text.
   * @param highlightedBackground the color
   */
  public void setHighlightedBackground(Color highlightedBackground) {
    if (highlightedBackground == null) {
      throw new NullPointerException("argument 'highlightedBackground' cannot be null");
    }
    this.highlightedBackground = highlightedBackground;
  }

  /**
   * The color of the gutter text.
   * @return the color
   */
  public Color getGutterText() {
    return gutterText;
  }

  /**
   * The color of the gutter text.
   * @param gutterText the color
   */
  public void setGutterText(Color gutterText) {
    if (gutterText == null) {
      throw new NullPointerException("argument 'gutterText' cannot be null");
    }
    this.gutterText = gutterText;
  }

  /**
   * The color of the border that joint the gutter and the script text area.
   * @return the color
   */
  public Color getGutterBorderColor() {
    return gutterBorderColor;
  }

  /**
   * The color of the border that joint the gutter and the script text area.
   * @param gutterBorderColor the color
   */
  public void setGutterBorderColor(Color gutterBorderColor) {
    if (gutterBorderColor == null) {
      throw new NullPointerException("argument 'gutterBorderColor' cannot be null");
    }
    this.gutterBorderColor = gutterBorderColor;
  }

  /**
   * The width of the border that joint the gutter and the script text area.
   * @return the width in pixel
   */
  public int getGutterBorderWidth() {
    return gutterBorderWidth;
  }

  /**
   * The width of the border that joint the gutter and the script text area.
   * @param gutterBorderWidth in pixel
   */
  public void setGutterBorderWidth(int gutterBorderWidth) {
    this.gutterBorderWidth = gutterBorderWidth;
  }

  /**
   * The font of the gutter text.
   * @return the font
   */
  public Font getGutterTextFont() {
    return gutterTextFont;
  }

  /**
   * The font of the gutter text.
   * @param gutterTextFont the font
   */
  public void setGutterTextFont(Font gutterTextFont) {
    if (gutterTextFont == null) {
      throw new NullPointerException("argument 'gutterTextFont' cannot be null");
    }
    this.gutterTextFont = gutterTextFont;
  }

  /**
   * The minimum padding from 'the leftmost of the line number text' to 
   * 'the left margin'.
   * @return the padding in pixel
   */
  public int getGutterTextPaddingLeft() {
    return gutterTextPaddingLeft;
  }

  /**
   * The minimum padding from 'the leftmost of the line number text' to 
   * 'the left margin'.
   * @param gutterTextPaddingLeft in pixel
   */
  public void setGutterTextPaddingLeft(int gutterTextPaddingLeft) {
    this.gutterTextPaddingLeft = gutterTextPaddingLeft;
  }

  /**
   * The minimum padding from 'the rightmost of the line number text' to 
   * 'the right margin' (not to the gutter border).
   * @return the padding in pixel
   */
  public int getGutterTextPaddingRight() {
    return gutterTextPaddingRight;
  }

  /**
   * The minimum padding from 'the rightmost of the line number text' to 
   * 'the right margin' (not to the gutter border).
   * @param gutterTextPaddingRight in pixel
   */
  public void setGutterTextPaddingRight(int gutterTextPaddingRight) {
    this.gutterTextPaddingRight = gutterTextPaddingRight;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Theme clone() {
    Theme object = null;
    try {
      object = (Theme) super.clone();
      object.styles = new HashMap<String, Style>();
      for (String key : styles.keySet()) {
        object.styles.put(key, styles.get(key).clone());
      }
    } catch (CloneNotSupportedException ex) {
    }
    return object;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();

    sb.append("[");
    sb.append(getClass().getName());
    sb.append(": ");
    sb.append("font: ").append(getFont());
    sb.append("; ");
    sb.append("background: ").append(getBackground());
    sb.append("; ");
    sb.append("highlightedBackground: ").append(getHighlightedBackground());
    sb.append("; ");
    sb.append("gutterText: ").append(getGutterText());
    sb.append("; ");
    sb.append("gutterBorderColor: ").append(getGutterBorderColor());
    sb.append(", ");
    sb.append("gutterBorderWidth: ").append(getGutterBorderWidth());
    sb.append(", ");
    sb.append("gutterTextFont: ").append(getGutterTextFont());
    sb.append(", ");
    sb.append("gutterTextPaddingLeft: ").append(getGutterTextPaddingLeft());
    sb.append(", ");
    sb.append("gutterTextPaddingRight: ").append(getGutterTextPaddingRight());
    sb.append(", ");
    sb.append("styles: ");
    for (String _key : styles.keySet()) {
      sb.append(_key).append(":").append(styles.get(_key));
    }
    sb.append("]");

    return sb.toString();
  }
}
